/**
 * 版权所有(C)，上海海鼎信息工程股份有限公司，2009，所有权利保留。
 * 
 * 项目名：	rumba-widget2
 * 文件名：	RPopupMenu.java
 * 模块说明：	
 * 修改历史：
 * Jan 18, 2009 - lxm - 创建。
 */
package com.hd123.rumba.gwt.widget2.client.menu;

import java.util.ArrayList;
import java.util.List;

import com.google.gwt.event.logical.shared.CloseEvent;
import com.google.gwt.event.logical.shared.CloseHandler;
import com.google.gwt.event.logical.shared.HasOpenHandlers;
import com.google.gwt.event.logical.shared.OpenEvent;
import com.google.gwt.event.logical.shared.OpenHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.Widget;
import com.hd123.rumba.gwt.widget2.client.panel.RDecoratorPanel;

/**
 * 弹出式菜单
 * <p>
 * 
 * <b>CSS Style Rules<b>
 * <ol type=disc>
 * <li>.rb-PopupMenu {}</li>
 * </ol>
 * 
 * @author lxm
 * 
 */
public class RPopupMenu extends PopupPanel implements HasOpenHandlers<RPopupMenu> {

  private static final String STYLENAME_DEFAULT = "rb-PopupMenu";

  public static final RectSide top = RectSide.topLeft;
  public static final RectSide right = RectSide.rightTop;
  public static final RectSide bottom = RectSide.bottomLeft;
  public static final RectSide left = RectSide.leftTop;

  /** 靠近顶边缘，左边对齐。 */
  public static final RectSide topLeft = RectSide.topLeft;
  /** 靠近顶边缘，右边对齐。 */
  public static final RectSide topRight = RectSide.topRight;
  /** 靠近右边缘，顶边对齐。 */
  public static final RectSide rightTop = RectSide.rightTop;
  /** 靠近右边缘，底边对齐。 */
  public static final RectSide rightBottom = RectSide.rightBottom;
  /** 靠近底边缘，左边对齐。 */
  public static final RectSide bottomLeft = RectSide.bottomLeft;
  /** 靠近底边缘，右边对齐。 */
  public static final RectSide bottomRight = RectSide.bottomRight;
  /** 靠近左边缘，顶边对齐。 */
  public static final RectSide leftTop = RectSide.leftTop;
  /** 靠近左边缘，底边对齐。 */
  public static final RectSide leftBottom = RectSide.leftBottom;

  /**
   * 创建对象实例。
   */
  public RPopupMenu() {
    super(true);
    drawSelf();
  }

  private FlexTable table;
  private RPopupMenu parentMenu;
  /** 所包含的子菜单项。List of IsRMenuItem。 */
  private List items = new ArrayList();
  private IsRMenuItem selectedItem;
  private RPopupMenu shownSubMenu;
  private Widget contextWidget;

  /**
   * 取得当前被选中的菜单项。
   * 
   * @return
   */
  public IsRMenuItem getSelectedItem() {
    return selectedItem;
  }

  /**
   * 清除所有包含的菜单项。
   */
  public void clearItems() {
    for (int row = table.getRowCount() - 1; row >= 0; row--)
      table.removeRow(row);
    items.clear();
  }

  /**
   * 添加菜单项。
   * 
   * @param item
   *            指定被添加的菜单项。not null，并且必须是尚未被添加到任何菜单。
   */
  public void addItem(IsRMenuItem item) {
    if (item == null)
      throw new RuntimeException("指定的参数item不允许为null。");
    if (items.contains(item))
      return;
    if (item.getParentMenu() != null)
      throw new RuntimeException("菜单项只能被加入到一个的菜单中。");

    item.setParentMenu(this);
    item.setSelectionStyle(false);
    items.add(item);

    int row = items.indexOf(item);
    table.setWidget(row, 0, item.toWidget());
    mergeSeperators();
  }

  /**
   * 删除指定的菜单项。
   * 
   * @param item
   *            指定被删除的菜单项。
   */
  public void removeItem(IsRMenuItem item) {
    int row = items.indexOf(item);
    if (row == -1)
      return;

    item.setParentMenu(null);
    items.remove(item);
    table.removeRow(row);
    mergeSeperators();
  }

  /**
   * 当某个菜单项的visible属性被修改时被调用。
   * 
   * @param item
   *            指定被设置的菜单项，若指定的菜单项不属于当前菜单则不作任何事。
   */
  public void onItemSetVisible(IsRMenuItem item) {
    int row = items.indexOf(item);
    if (row == -1)
      return;
    mergeSeperators();
  }

  public void onBrowserEvent(Event event) {
    super.onBrowserEvent(event);

    IsRMenuItem item = findItem(DOM.eventGetTarget(event));
    switch (DOM.eventGetType(event)) {
    case Event.ONCLICK: {
      if (item != null) {
        item.onClick();
      }
      closeAllParents();
      break;
    }

    case Event.ONMOUSEOVER: {
      setSelectedItem(item);
      if (item != null) {
        item.onMouseOver();
        if (item.isEnabled())
          showSubMenu(item);
      }
      break;
    }

    case Event.ONMOUSEOUT: {
      if (item != null) {
        item.onMouseOver();
      }
      break;
    }
    }
  }

  public void show() {
    OpenEvent.fire(this, this);
    super.show();
    setSelectedItem(null);
  }

  /**
   * 将菜单显示出来。
   * 
   * @param contextWidget
   *            指定上下文控件，即引起菜单被弹出的控件。
   * @param besideWidget
   *            指定作为计算依据的屏幕部件。 null表示位于屏幕左上角，宽度和高度都为0的矩形区域。
   * @param side
   *            指定优先将菜单显示在besideWidget的一个矩形边上。null表示bottom。
   */
  public void show(Widget contextWidget, Widget besideWidget, RectSide side) {
    this.contextWidget = contextWidget;
    
    final int[] widgetRect = new int[4];
    if (besideWidget != null) {
      widgetRect[RECT_LEFT] = besideWidget.getAbsoluteLeft();
      widgetRect[RECT_TOP] = besideWidget.getAbsoluteTop();
      widgetRect[RECT_WIDTH] = besideWidget.getOffsetWidth();
      widgetRect[RECT_HEIGHT] = besideWidget.getOffsetHeight();
    }
    final RectSide side2 = side == null ? bottom : side;

    setPopupPositionAndShow(new PositionCallback() {
      public void setPosition(int i, int j) {
        if (side2 == top || side2 == bottom)
          showVerticalSide(widgetRect, side2);
        else
          showHorizontalSide(widgetRect, side2);
      }
    });
  }

  public HandlerRegistration addOpenHandler(OpenHandler<RPopupMenu> handler) {
    return addHandler(handler, OpenEvent.getType());
  }

  void setParentMenu(RPopupMenu menu) {
    this.parentMenu = menu;
  }

  /** 上下文控件，即引起菜单被弹出的上下文控件。 */
  public Widget getContextWidget() {
    return parentMenu != null ? parentMenu.getContextWidget() : contextWidget;
  }

  private void drawSelf() {
    RDecoratorPanel pnl = new RDecoratorPanel();
    pnl.setStyleName(STYLENAME_DEFAULT);

    table = new FlexTable();
    table.setCellPadding(0);
    table.setCellSpacing(0);

    pnl.setWidget(table);

    setWidget(pnl);
    sinkEvents(Event.ONCLICK | Event.ONMOUSEOVER | Event.ONMOUSEOUT);
    setStyleName(STYLENAME_DEFAULT);

    addCloseHandler(new Handler_self());
  }

  private void closeAllParents() {
    RPopupMenu curMenu = this;
    while (curMenu != null) {
      curMenu.hide();
      //
      // if ((curMenu.parentMenu == null) && (curMenu.selectedItem != null)) {
      // curMenu.selectedItem.setSelectionStyle(false);
      // curMenu.selectedItem = null;
      // }

      curMenu = curMenu.parentMenu;
    }
  }

  /** 当菜单被隐藏的同时将其所有显示的子菜单一起隐藏。 */
  private void onHide() {
    if (shownSubMenu != null) {
      shownSubMenu.onHide();
      shownSubMenu.hide();
    }
  }

  private IsRMenuItem findItem(Element element) {
    for (int i = 0; i < items.size(); ++i) {
      IsRMenuItem item = (IsRMenuItem) items.get(i);
      if (item.isOrHasChild(element)) {
        return item;
      }
    }
    return null;
  }

  private void setSelectedItem(IsRMenuItem item) {
    if (selectedItem == item)
      return;
    if (selectedItem != null)
      selectedItem.setSelectionStyle(false);
    selectedItem = item;
    if (selectedItem != null)
      selectedItem.setSelectionStyle(true);
  }

  /** 自动合并分隔条。 */
  private void mergeSeperators() {
    IsRMenuItem firstVisible = null;
    IsRMenuItem lastVisible = null;
    for (int row = 0, n = items.size(); row < n; row++) {
      IsRMenuItem item = (IsRMenuItem) items.get(row);

      // 处理所有不可见的菜单项。
      if (!item.isVisible()) {
        table.getRowFormatter().setVisible(row, false);
        continue;
      }

      // 判断并设施取得第一条可见的菜单项。
      if (firstVisible == null)
        firstVisible = item;

      // 如果是连续的分隔条，设置其为不可见。
      if (lastVisible != null && lastVisible.asSeperator())
        if (item.asSeperator()) {
          table.getRowFormatter().setVisible(row, false);
          continue;
        }

      lastVisible = item;
      table.getRowFormatter().setVisible(row, true);
    }

    // 如果第一条可见的菜单项是分隔条，则设置其不可见。
    if (firstVisible != null && firstVisible.asSeperator()) {
      int row = items.indexOf(firstVisible);
      table.getRowFormatter().setVisible(row, false);
    }

    // 如果最后一条可见的菜单项是分隔条，则设置其不可见。
    if (lastVisible != null && lastVisible.asSeperator()) {
      int row = items.indexOf(lastVisible);
      table.getRowFormatter().setVisible(row, false);
    }
  }

  private void showSubMenu(IsRMenuItem item) {
    if (shownSubMenu != null)
      shownSubMenu.hide();
    shownSubMenu = item.getSubMenu();
    if (shownSubMenu != null)
      shownSubMenu.show(null, item.toWidget(), right);
  }

  private static final int RECT_LEFT = 0;
  private static final int RECT_TOP = 1;
  private static final int RECT_WIDTH = 2;
  private static final int RECT_HEIGHT = 3;

  private void showVerticalSide(int[] widgetRect, RectSide side) {
    int[] menuRect = new int[4];
    menuRect[RECT_WIDTH] = getOffsetWidth();
    menuRect[RECT_HEIGHT] = getOffsetHeight();

    side.move(menuRect, widgetRect);
    if (!allInViewVertical(menuRect)) {
      // 尝试对边，如果还无法全部看到，则放弃对边的尝试。
      side.opposite().move(menuRect, widgetRect);
      if (!allInViewVertical(menuRect))
        side.move(menuRect, widgetRect);
    }
    moveToViewVertical(menuRect);
    moveToViewHorizontal(menuRect);

    setPopupPosition(menuRect[RECT_LEFT], menuRect[RECT_TOP]);
  }

  /** 判断垂直方向是否全部可见。 */
  private boolean allInViewVertical(int[] menuRect) {
    if (menuRect[RECT_TOP] < Window.getScrollTop())
      return false;
    if (menuRect[RECT_TOP] > Window.getScrollTop() + Window.getClientHeight())
      return false;
    if (menuRect[RECT_TOP] + menuRect[RECT_HEIGHT] < Window.getScrollTop())
      return false;
    if (menuRect[RECT_TOP] + menuRect[RECT_HEIGHT] > Window.getScrollTop()
        + Window.getClientHeight())
      return false;
    return true;
  }

  private void showHorizontalSide(int[] widgetRect, RectSide side) {
    int[] menuRect = new int[4];
    menuRect[RECT_WIDTH] = getOffsetWidth();
    menuRect[RECT_HEIGHT] = getOffsetHeight();

    side.move(menuRect, widgetRect);
    if (!allInViewHorizontal(menuRect)) {
      // 尝试对边，如果还无法全部看到，则放弃对边的尝试。
      side.opposite().move(menuRect, widgetRect);
      if (!allInViewHorizontal(menuRect))
        side.move(menuRect, widgetRect);
    }
    moveToViewHorizontal(menuRect);
    moveToViewVertical(menuRect);

    setPopupPosition(menuRect[RECT_LEFT], menuRect[RECT_TOP]);
  }

  /** 判断水平方向是否全部可见。 */
  private boolean allInViewHorizontal(int[] menuRect) {
    if (menuRect[RECT_LEFT] < Window.getScrollLeft())
      return false;
    if (menuRect[RECT_LEFT] > Window.getScrollLeft() + Window.getClientWidth())
      return false;
    if (menuRect[RECT_LEFT] + menuRect[RECT_WIDTH] < Window.getScrollLeft())
      return false;
    if (menuRect[RECT_LEFT] + menuRect[RECT_WIDTH] > Window.getScrollLeft()
        + Window.getClientWidth())
      return false;
    return true;
  }

  /** 在垂直方向上尽可能移动到可见。 */
  private void moveToViewVertical(int[] menuRect) {
    if (menuRect[RECT_TOP] + menuRect[RECT_HEIGHT] > Window.getScrollTop()
        + Window.getClientHeight())
      menuRect[RECT_TOP] = Window.getScrollTop() + Window.getClientHeight() - menuRect[RECT_HEIGHT];
    if (menuRect[RECT_TOP] < Window.getScrollTop())
      menuRect[RECT_TOP] = Window.getScrollTop();
  }

  /** 在水平方向上尽可能移动到可见。 */
  private void moveToViewHorizontal(int[] menuRect) {
    if (menuRect[RECT_LEFT] + menuRect[RECT_WIDTH] > Window.getScrollLeft()
        + Window.getClientWidth())
      menuRect[RECT_LEFT] = Window.getScrollLeft() + Window.getClientWidth() - menuRect[RECT_WIDTH];
    if (menuRect[RECT_LEFT] < Window.getScrollLeft())
      menuRect[RECT_LEFT] = Window.getScrollLeft();
  }

  private class Handler_self implements CloseHandler {

    public void onClose(CloseEvent event) {
      if (event.isAutoClosed())
        closeAllParents();

      onHide();
      shownSubMenu = null;
    }

  }

  private static enum RectSide {
    /** 靠近顶边缘，左边对齐。 */
    topLeft,
    /** 靠近顶边缘，右边对齐。 */
    topRight,
    /** 靠近右边缘，顶边对齐。 */
    rightTop,
    /** 靠近右边缘，底边对齐。 */
    rightBottom,
    /** 靠近底边缘，左边对齐。 */
    bottomLeft,
    /** 靠近底边缘，右边对齐。 */
    bottomRight,
    /** 靠近左边缘，顶边对齐。 */
    leftTop,
    /** 靠近左边缘，底边对齐。 */
    leftBottom;

    public RectSide opposite() {
      if (this == topLeft)
        return bottomLeft;
      else if (this == topRight)
        return bottomRight;
      else if (this == rightTop)
        return leftTop;
      else if (this == rightBottom)
        return leftBottom;
      else if (this == bottomLeft)
        return topLeft;
      else if (this == bottomRight)
        return topRight;
      else if (this == leftTop)
        return rightTop;
      else if (this == leftBottom)
        return rightBottom;
      else
        throw new RuntimeException("Unknown RectSide!");
    }

    public void move(int rect[], int[] refRect) {
      assert rect != null;
      assert refRect != null;
      if (this == topLeft) {
        rect[RECT_LEFT] = refRect[RECT_LEFT];
        rect[RECT_TOP] = refRect[RECT_TOP] - rect[RECT_HEIGHT];
      } else if (this == topRight) {
        rect[RECT_LEFT] = refRect[RECT_LEFT] + refRect[RECT_WIDTH] - rect[RECT_WIDTH];
        rect[RECT_TOP] = refRect[RECT_TOP] - rect[RECT_HEIGHT];
      } else if (this == rightTop) {
        rect[RECT_LEFT] = refRect[RECT_LEFT] + refRect[RECT_WIDTH];
        rect[RECT_TOP] = refRect[RECT_TOP];
      } else if (this == rightBottom) {
        rect[RECT_LEFT] = refRect[RECT_LEFT] + refRect[RECT_WIDTH];
        rect[RECT_TOP] = refRect[RECT_TOP] + refRect[RECT_HEIGHT] - rect[RECT_HEIGHT];
      } else if (this == bottomLeft) {
        rect[RECT_LEFT] = refRect[RECT_LEFT];
        rect[RECT_TOP] = refRect[RECT_TOP] + refRect[RECT_HEIGHT];
      } else if (this == bottomRight) {
        rect[RECT_LEFT] = refRect[RECT_LEFT] + refRect[RECT_WIDTH] - rect[RECT_WIDTH];
        rect[RECT_TOP] = refRect[RECT_TOP] + refRect[RECT_HEIGHT];
      } else if (this == leftTop) {
        rect[RECT_LEFT] = refRect[RECT_LEFT] - refRect[RECT_WIDTH];
        rect[RECT_TOP] = refRect[RECT_TOP];
      } else if (this == leftBottom) {
        rect[RECT_LEFT] = refRect[RECT_LEFT] - refRect[RECT_WIDTH];
        rect[RECT_TOP] = refRect[RECT_TOP] + refRect[RECT_HEIGHT] - rect[RECT_HEIGHT];
      } else
        throw new RuntimeException("Unknown RectSide!");
    }
  }
}
